/***
 *
 *  rtmac/tdma/tdma_module.c
 *
 *  RTmac - real-time networking media access control subsystem
 *  Copyright (C) 2002      Marc Kleine-Budde <kleine-budde@gmx.de>,
 *                2003-2005 Jan Kiszka <Jan.Kiszka@web.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <asm/div64.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>

#include <rtdm/driver.h>
#include <rtmac/rtmac_vnic.h>
#include <rtmac/tdma/tdma.h>
#include <rtmac/tdma/tdma_dev.h>
#include <rtmac/tdma/tdma_ioctl.h>
#include <rtmac/tdma/tdma_proto.h>
#include <rtmac/tdma/tdma_worker.h>

#ifdef CONFIG_XENO_OPT_VFILE
int tdma_proc_read(struct xnvfile_regular_iterator *it, void *data)
{
	int d, err = 0;
	struct rtnet_device *rtdev;
	struct tdma_priv *tdma;
	const char *state;
#ifdef CONFIG_XENO_DRIVERS_NET_TDMA_MASTER
	u64 cycle;
#endif

	xnvfile_printf(it, "Interface       API Device      Operation Mode  "
			   "Cycle   State\n");

	for (d = 1; d <= MAX_RT_DEVICES; d++) {
		rtdev = rtdev_get_by_index(d);
		if (!rtdev)
			continue;

		err = mutex_lock_interruptible(&rtdev->nrt_lock);
		if (err < 0) {
			rtdev_dereference(rtdev);
			break;
		}

		if (!rtdev->mac_priv)
			goto unlock_dev;
		tdma = (struct tdma_priv *)rtdev->mac_priv->disc_priv;

		xnvfile_printf(it, "%-15s %-15s ", rtdev->name,
			       tdma->api_device.name);

		if (test_bit(TDMA_FLAG_CALIBRATED, &tdma->flags)) {
#ifdef CONFIG_XENO_DRIVERS_NET_TDMA_MASTER
			if (test_bit(TDMA_FLAG_BACKUP_MASTER, &tdma->flags) &&
			    !test_bit(TDMA_FLAG_BACKUP_ACTIVE, &tdma->flags))
				state = "stand-by";
			else
#endif /* CONFIG_XENO_DRIVERS_NET_TDMA_MASTER */
				state = "active";
		} else
			state = "init";
#ifdef CONFIG_XENO_DRIVERS_NET_TDMA_MASTER
		if (test_bit(TDMA_FLAG_MASTER, &tdma->flags)) {
			cycle = tdma->cycle_period + 500;
			do_div(cycle, 1000);
			if (test_bit(TDMA_FLAG_BACKUP_MASTER, &tdma->flags))
				xnvfile_printf(it, "Backup Master   %-7ld %s\n",
					       (unsigned long)cycle, state);
			else
				xnvfile_printf(it, "Master          %-7ld %s\n",
					       (unsigned long)cycle, state);
		} else
#endif /* CONFIG_XENO_DRIVERS_NET_TDMA_MASTER */
			xnvfile_printf(it, "Slave           -       %s\n",
				       state);

	unlock_dev:
		mutex_unlock(&rtdev->nrt_lock);
		rtdev_dereference(rtdev);
	}

	return err;
}

int tdma_slots_proc_read(struct xnvfile_regular_iterator *it, void *data)
{
	int d, i, err = 0;
	struct rtnet_device *rtdev;
	struct tdma_priv *tdma;
	struct tdma_slot *slot;
	int jnt_id;
	u64 slot_offset;

	xnvfile_printf(it, "Interface       "
			   "Slots (id[->joint]:offset:phasing/period:size)\n");

	for (d = 1; d <= MAX_RT_DEVICES; d++) {
		rtdev = rtdev_get_by_index(d);
		if (!rtdev)
			continue;

		err = mutex_lock_interruptible(&rtdev->nrt_lock);
		if (err < 0) {
			rtdev_dereference(rtdev);
			break;
		}

		if (!rtdev->mac_priv)
			goto unlock_dev;
		tdma = (struct tdma_priv *)rtdev->mac_priv->disc_priv;

		xnvfile_printf(it, "%-15s ", rtdev->name);

#ifdef CONFIG_XENO_DRIVERS_NET_TDMA_MASTER
		if (test_bit(TDMA_FLAG_BACKUP_MASTER, &tdma->flags)) {
			slot_offset = tdma->backup_sync_inc -
				      tdma->cycle_period + 500;
			do_div(slot_offset, 1000);
			xnvfile_printf(it, "bak:%ld  ",
				       (unsigned long)slot_offset);
		}
#endif /* CONFIG_XENO_DRIVERS_NET_TDMA_MASTER */

		if (tdma->slot_table)
			for (i = 0; i <= tdma->max_slot_id; i++) {
				slot = tdma->slot_table[i];
				if (!slot ||
				    ((i == DEFAULT_NRT_SLOT) &&
				     (tdma->slot_table[DEFAULT_SLOT] == slot)))
					continue;

				if (slot->queue == &slot->local_queue) {
					xnvfile_printf(it, "%d", i);
				} else
					for (jnt_id = 0;
					     jnt_id <= tdma->max_slot_id;
					     jnt_id++)
						if (&tdma->slot_table[jnt_id]
							     ->local_queue ==
						    slot->queue) {
							xnvfile_printf(it,
								       "%d->%d",
								       i,
								       jnt_id);
							break;
						}

				slot_offset = slot->offset + 500;
				do_div(slot_offset, 1000);
				xnvfile_printf(it, ":%ld:%d/%d:%d  ",
					       (unsigned long)slot_offset,
					       slot->phasing + 1, slot->period,
					       slot->mtu);
			}

		xnvfile_printf(it, "\n");

	unlock_dev:
		mutex_unlock(&rtdev->nrt_lock);
		rtdev_dereference(rtdev);
	}

	return err;
}
#endif /* CONFIG_XENO_OPT_VFILE */

int tdma_attach(struct rtnet_device *rtdev, void *priv)
{
	struct tdma_priv *tdma = (struct tdma_priv *)priv;
	int ret;

	memset(tdma, 0, sizeof(struct tdma_priv));

	tdma->magic = TDMA_MAGIC;
	tdma->rtdev = rtdev;

	rtdm_lock_init(&tdma->lock);

	rtdm_event_init(&tdma->worker_wakeup, 0);
	rtdm_event_init(&tdma->xmit_event, 0);
	rtdm_event_init(&tdma->sync_event, 0);

	ret = tdma_dev_init(rtdev, tdma);
	if (ret < 0)
		goto err_out1;

	ret = rtdm_task_init(&tdma->worker_task, "rtnet-tdma", tdma_worker,
			     tdma, DEF_WORKER_PRIO, 0);
	if (ret != 0)
		goto err_out2;

	return 0;

err_out2:
	tdma_dev_release(tdma);

err_out1:
	rtdm_event_destroy(&tdma->sync_event);
	rtdm_event_destroy(&tdma->xmit_event);
	rtdm_event_destroy(&tdma->worker_wakeup);

	return ret;
}

int tdma_detach(struct rtnet_device *rtdev, void *priv)
{
	struct tdma_priv *tdma = (struct tdma_priv *)priv;
	struct tdma_job *job, *tmp;

	rtdm_event_destroy(&tdma->sync_event);
	rtdm_event_destroy(&tdma->xmit_event);
	rtdm_event_destroy(&tdma->worker_wakeup);

	tdma_dev_release(tdma);

	rtdm_task_destroy(&tdma->worker_task);

	list_for_each_entry_safe (job, tmp, &tdma->first_job->entry, entry) {
		if (job->id >= 0)
			tdma_cleanup_slot(tdma, SLOT_JOB(job));
		else if (job->id == XMIT_RPL_CAL) {
			__list_del(job->entry.prev, job->entry.next);
			kfree_rtskb(REPLY_CAL_JOB(job)->reply_rtskb);
		}
	}

	if (tdma->slot_table)
		kfree(tdma->slot_table);

#ifdef CONFIG_XENO_DRIVERS_NET_TDMA_MASTER
	if (test_bit(TDMA_FLAG_MASTER, &tdma->flags))
		rtskb_pool_release(&tdma->cal_rtskb_pool);
#endif

	return 0;
}

#ifdef CONFIG_XENO_OPT_VFILE
struct rtmac_proc_entry tdma_proc_entries[] = {
	{ name: "tdma", handler: tdma_proc_read },
	{ name: "tdma_slots", handler: tdma_slots_proc_read },
};
#endif /* CONFIG_XENO_OPT_VFILE */

struct rtmac_disc tdma_disc = {
	name: "TDMA",
	priv_size: sizeof(struct tdma_priv),
	disc_type: __constant_htons(RTMAC_TYPE_TDMA),

	packet_rx: tdma_packet_rx,
	rt_packet_tx: tdma_rt_packet_tx,
	nrt_packet_tx: tdma_nrt_packet_tx,

	get_mtu: tdma_get_mtu,

	vnic_xmit: RTMAC_DEFAULT_VNIC,

	attach: tdma_attach,
	detach: tdma_detach,

	ioctls: {
		service_name: "RTmac/TDMA",
		ioctl_type: RTNET_IOC_TYPE_RTMAC_TDMA,
		handler: tdma_ioctl
	},

#ifdef CONFIG_XENO_OPT_VFILE
	proc_entries: tdma_proc_entries,
	nr_proc_entries: ARRAY_SIZE(tdma_proc_entries),
#endif /* CONFIG_XENO_OPT_VFILE */
};

int __init tdma_init(void)
{
	int ret;

	printk("RTmac/TDMA: init time division multiple access control "
	       "mechanism\n");

	ret = rtmac_disc_register(&tdma_disc);
	if (ret < 0)
		return ret;

	return 0;
}

void tdma_release(void)
{
	rtmac_disc_deregister(&tdma_disc);

	printk("RTmac/TDMA: unloaded\n");
}

module_init(tdma_init);
module_exit(tdma_release);

MODULE_AUTHOR("Jan Kiszka");
MODULE_LICENSE("GPL");
